//
//  NSObject+TFCore.m
//  TFFoundation
//
//  Created by TFAppleWork-Summer on 2017/3/10.
//  Copyright © 2017年 TFAppleWork-Summer. All rights reserved.
//

#import "NSObject+TFCore.h"
#import <objc/runtime.h>

static const int block_key;

@interface _TFNSObjectKVOBlockTarget : NSObject

@property (nonatomic, copy) void (^block)(__weak id obj, id oldVal, id newVal);

- (id)initWithBlock:(void (^)(__weak id obj, id oldVal, id newVal))block;

@end

@implementation _TFNSObjectKVOBlockTarget

- (id)initWithBlock:(void (^)(__weak id obj, id oldVal, id newVal))block {
    self = [super init];
    if (self) {
        self.block = block;
    }
    return self;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if (!self.block) return;
    
    BOOL isPrior = [[change objectForKey:NSKeyValueChangeNotificationIsPriorKey] boolValue];
    if (isPrior) return;
    
    NSKeyValueChange changeKind = [[change objectForKey:NSKeyValueChangeKindKey] integerValue];
    if (changeKind != NSKeyValueChangeSetting) return;
    
    id oldVal = [change objectForKey:NSKeyValueChangeOldKey];
    if (oldVal == [NSNull null]) oldVal = nil;
    
    id newVal = [change objectForKey:NSKeyValueChangeNewKey];
    if (newVal == [NSNull null]) newVal = nil;
    
    self.block(object, oldVal, newVal);
}

@end

@implementation NSObject (TFCore)

#pragma mark - 常用类方法

+ (NSArray<NSString *> *)tf_getMethodNameList {
    return [self tf_getMethodNameListWithPredicateBlock:nil];
}

+ (NSArray<NSString *> *)tf_getMethodNameListWithPredicateBlock:(BOOL (^)(NSString *))predicateBlock {
    u_int count;
    Method *methodList = class_copyMethodList([self class], &count);
    NSMutableArray *listArray = [NSMutableArray array];
    for (int i=0; i<count; i++) {
        // method_getImplementation  由Method得到IMP函数指针
        Method method = methodList[i];
        SEL selector = method_getName(method);
        NSString *selectorName = NSStringFromSelector(selector);
        BOOL isMatch = YES;
        if (predicateBlock) {
            isMatch = predicateBlock(selectorName);
        }
        if (isMatch) {
            [listArray addObject:selectorName];
        }
    }
    return [NSArray arrayWithArray:listArray];
}

+ (void)tf_enumeratePropertyListWithBlock:(TFPropertyEnumerateBlock)block {
    u_int count;
    objc_property_t *propertyList = class_copyPropertyList([self class], &count);
    for (int i=0; i<count; i++) {
        objc_property_t property = propertyList[i];
        NSString *propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
        NSString *propertyAttributes = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
        Class modelClass = nil;
        NSString *propertyTypeName = nil;
        if ([propertyAttributes hasPrefix:@"T@"]) {
            //说明是一个对象类型
            propertyTypeName = [propertyAttributes componentsSeparatedByString:@"\""][1];
            if ([propertyTypeName hasPrefix:@"NSArray"]|[propertyTypeName hasPrefix:@"NSMutableArray"]) {
                
                NSString *arrayModelClassName = [[[[propertyTypeName componentsSeparatedByString:@"<"] lastObject] componentsSeparatedByString:@">"] firstObject];
                if ([arrayModelClassName hasSuffix:@"*"]) {
                    //去除*
                    arrayModelClassName = [arrayModelClassName stringByReplacingOccurrencesOfString:@"*" withString:@""];
                }
                //去除空格
                arrayModelClassName = [arrayModelClassName stringByReplacingOccurrencesOfString:@" " withString:@""];
                modelClass = NSClassFromString(arrayModelClassName);
                
            }
            else {
                modelClass = NSClassFromString(propertyTypeName);
            }
        }
        else {
            //整形、浮点型
            if ([propertyAttributes hasPrefix:@"Tf"]) {
                propertyTypeName = @"float";
            }
            else if ([propertyAttributes hasPrefix:@"Td"]) {
                //double
                propertyTypeName = @"double";
            }
            else if ([propertyAttributes hasPrefix:@"TB"]) {
                propertyTypeName = @"BOOL";
            }
            else if ([propertyAttributes hasPrefix:@"Tq"]) {
                propertyTypeName = @"NSInteger";
            }
            else if ([propertyAttributes hasPrefix:@"Ti"]|[propertyAttributes hasPrefix:@"Tl"]|[propertyAttributes hasPrefix:@"Ts"]|[propertyAttributes hasPrefix:@"TI"]) {
                propertyTypeName = @"int";
            }
            else if ([propertyAttributes hasPrefix:@"Tc"]) {
                propertyTypeName = @"char";
            }
            else {
                propertyTypeName = @"";
            }
        }
        block(propertyName,propertyTypeName,modelClass);
    }
}

+ (NSArray<NSString *> *)tf_getPropertyNameList {
    __block NSMutableArray *listArray = [NSMutableArray array];
    [[self class] tf_enumeratePropertyListWithBlock:^(NSString * _Nonnull propertyName, NSString * _Nonnull propertyTypeName, Class  _Nullable __unsafe_unretained propertyClass) {
        [listArray addObject:propertyName];
    }];
    return [NSArray arrayWithArray:listArray];
}

+ (BOOL)tf_swizzleInstanceMethod:(SEL)originalSel with:(SEL)newSel {
    Method originalMethod = class_getInstanceMethod(self, originalSel);
    Method newMethod = class_getInstanceMethod(self, newSel);
    if (!originalMethod || !newMethod) return NO;
    
    class_addMethod(self,
                    originalSel,
                    class_getMethodImplementation(self, originalSel),
                    method_getTypeEncoding(originalMethod));
    class_addMethod(self,
                    newSel,
                    class_getMethodImplementation(self, newSel),
                    method_getTypeEncoding(newMethod));
    
    method_exchangeImplementations(class_getInstanceMethod(self, originalSel),
                                   class_getInstanceMethod(self, newSel));
    return YES;
}

+ (BOOL)tf_swizzleClassMethod:(SEL)originalSel with:(SEL)newSel {
    Class class = object_getClass(self);
    Method originalMethod = class_getInstanceMethod(class, originalSel);
    Method newMethod = class_getInstanceMethod(class, newSel);
    if (!originalMethod || !newMethod) return NO;
    method_exchangeImplementations(originalMethod, newMethod);
    return YES;
}

+ (NSString *)tf_classNameString {
    return NSStringFromClass(self);
}

+ (NSArray<Class> *)tf_subClasses {
    int numClasses = objc_getClassList(NULL, 0);
    Class *classes = (Class*)malloc(sizeof(Class) * numClasses);
    
    numClasses = objc_getClassList(classes, numClasses);
    
    NSMutableArray *result = [NSMutableArray array];
    for(NSInteger i=0; i<numClasses; i++) {
        Class cls = classes[i];
        do{
            cls = class_getSuperclass(cls);
        }while(cls && cls != [self class]);
        
        if(cls){
            [result addObject:classes[i]];
        }
    }
    free(classes);
    return [NSArray arrayWithArray:result];
}

#pragma mark - 常用实例方法

- (NSString *)tf_classNameString {
    return [[self class]tf_classNameString];
}

- (void)tf_setAssociateValue:(id)value withkey:(void *)key assoicationPolicy:(TFAssociationPolicy)assoicationPolicy {
    objc_setAssociatedObject(self, key, value, (objc_AssociationPolicy)assoicationPolicy);
}

- (id)tf_getAssociateValueForKey:(void *)key {
    return objc_getAssociatedObject(self, key);
}

- (void)tf_removeAssociatedValues {
    objc_removeAssociatedObjects(self);
}

- (id)tf_propertyValueForName:(NSString *)name {
    objc_property_t property = class_getProperty([self class], [name UTF8String]);
    if (property) {
        return [self valueForKey:name];
    }
    return nil;
}

- (id)tf_propertyValueForKeyPath:(NSString *)keyPath {
    if (keyPath.length) {
        NSArray *propertyNameArray = [keyPath componentsSeparatedByString:@"."];
        id obj = self;
        for (NSString *name in propertyNameArray) {
            obj = [obj tf_propertyValueForName:name];
            if (!obj) {
                return nil;
            }
        }
        return obj;
    }
    return nil;
}

- (BOOL)tf_isEqualObject:(id)object withKeyPath:(NSString *)keyPath {
    if (keyPath.length) {
        return [[self tf_propertyValueForKeyPath:keyPath] isEqual:[object tf_propertyValueForKeyPath:keyPath]];
    }
    return [self isEqual:object];
}

- (BOOL)tf_isPropertiesEqualObject:(id)object {
    //获取所有的属性然后进行对比
    id currentObject = self;
    id compareObject = object;
    if (currentObject&&compareObject) {
        if ([currentObject p_IsSystemObject]|[compareObject p_IsSystemObject]) {
            return [currentObject isEqual:compareObject];
        }
        else {
            NSArray *propertyNamesArray = [[object class] tf_getPropertyNameList];
            for (NSString *propertyName in propertyNamesArray) {
                //获取对应的值
                id currentPropertyValue = [currentObject tf_propertyValueForName:propertyName];
                id comparePropertyValue = [compareObject tf_propertyValueForName:propertyName];
                if ([currentPropertyValue p_IsSystemObject]|[comparePropertyValue p_IsSystemObject]) {
                    if (![currentPropertyValue isEqual:comparePropertyValue]) {
                        return NO;
                    }
                }
                else {
                    return [currentPropertyValue tf_isPropertiesEqualObject:comparePropertyValue];
                }
            }
            return YES;
        }
        
    }
    else {
        return YES;
    }
}

- (BOOL)p_IsSystemObject {
    NSString *className = [self tf_classNameString];
    return ([className hasPrefix:@"NS"]|[className hasPrefix:@"__NS"]|[className hasPrefix:@"UI"]);
}

- (void)tf_addObserverBlockForKeyPath:(NSString *)keyPath block:(void (^)(__weak id obj, id oldVal, id newVal))block {
    if (!keyPath || !block) return;
    _TFNSObjectKVOBlockTarget *target = [[_TFNSObjectKVOBlockTarget alloc] initWithBlock:block];
    NSMutableDictionary *dic = [self _tf_allNSObjectObserverBlocks];
    NSMutableArray *arr = dic[keyPath];
    if (!arr) {
        arr = [NSMutableArray new];
        dic[keyPath] = arr;
    }
    [arr addObject:target];
    [self addObserver:target forKeyPath:keyPath options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
}

- (void)tf_removeObserverBlocksForKeyPath:(NSString *)keyPath {
    if (!keyPath) return;
    NSMutableDictionary *dic = [self _tf_allNSObjectObserverBlocks];
    NSMutableArray *arr = dic[keyPath];
    [arr enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) {
        [self removeObserver:obj forKeyPath:keyPath];
    }];
    
    [dic removeObjectForKey:keyPath];
}

- (void)tf_removeObserverBlocks {
    NSMutableDictionary *dic = [self _tf_allNSObjectObserverBlocks];
    [dic enumerateKeysAndObjectsUsingBlock: ^(NSString *key, NSArray *arr, BOOL *stop) {
        [arr enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) {
            [self removeObserver:obj forKeyPath:key];
        }];
    }];
    
    [dic removeAllObjects];
}

- (NSMutableDictionary *)_tf_allNSObjectObserverBlocks {
    NSMutableDictionary *targets = objc_getAssociatedObject(self, &block_key);
    if (!targets) {
        targets = [NSMutableDictionary new];
        objc_setAssociatedObject(self, &block_key, targets, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    return targets;
}



@end
